package com.iqiyi.pps.epg.core.utils;

import com.alibaba.fastjson.JSONObject;
import com.iqiyi.kiwi.utils.HttpClientConnection;
import com.iqiyi.pps.epg.core.constant.ApiConstant;
import com.iqiyi.pps.epg.core.constant.ProgramAttribute;
import com.iqiyi.pps.epg.core.model.channel.ChannelBase;
import com.iqiyi.pps.epg.core.model.channel.ChannelLog;
import com.iqiyi.pps.epg.core.model.program.ProgramData;
import com.iqiyi.pps.epg.core.service.channel.ChannelService;
import com.iqiyi.pps.epg.core.service.program.ProgramService;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.StringReader;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by karkarrotxu on 2015/2/2.
 */
public final class TVMaoUtil {
    public static final String DELETE = "delete";
    public static final String INSERT = "insert";
    public static final String UPDATE = "update";
    public static final String ERROR_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><document>错误的频道代码</document>";
    public static final String MOCK_FIREFOX = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3";
    private static Logger logger = LoggerFactory.getLogger(TVMaoUtil.class);
    private static ChannelService cService = null;
    private static ProgramService pService = null;

    public static void doQueryProgram(List<String> codeList, int numThread) {
        ProgramBasket basket = new ProgramBasket(codeList);
        ProgramConsumer handler = null;
        ExecutorService exec = Executors.newFixedThreadPool(numThread);
        for (int index = 0; index < numThread; index++) {
            handler = new ProgramConsumer(basket);
            exec.execute(handler);
        }
    }

    public static boolean syncQuery(String code) {
        String response = getResponse(code);
        List<String> resultList = new ArrayList<String>();
        if (response != null) {
            resultList.add(response);

            dealResponseList(resultList);
            return true;
        } else {
            return false;
        }
    }

    private static Document transToDoc(String detail) {
        if (detail == null) {
            return null;
        }
        StringReader sr = new StringReader(detail);
        InputSource is = new InputSource(sr);
        Document doc = null;
        SAXBuilder builder = new SAXBuilder(false);
        try {
            doc = builder.build(is);
        } catch (IOException e) {
            logger.error(e.getMessage());
        } catch (JDOMException e) {
            logger.error(e.getMessage());
        }
        return doc;
    }

    private static String getResponse(String code) {
        HttpClientConnection con = null;
        for (int i = 0; i < 5; i++) {
            con = new HttpClientConnection(ApiConstant.URL_TVMAO_EPG_DATA, HttpClientConnection.GET_METHOD);
            con.setSoTimeout(5000);
            con.addReqParam("secret", ApiConstant.SECRET_TVMAO);
            con.addReqParam("c", code);
            con.addReqHeader("User-Agent", MOCK_FIREFOX);
            con.connect();

            if (con.getStatus() == 200) {
                return con.getBody();
            }
        }
        logger.info("[getResponse] fail code:" + code);
        return null;
    }

    private static ChannelLog createLog(ChannelBase channelBase, DecodeResult decodeResult) {
        ChannelLog channelLog = new ChannelLog();
        channelLog.setType(Constants.LogType.UPDATE_TVMAO_DATA.getIndex());
        channelLog.setUser(Constants.SYSTEM_USER_SIGN);
        channelLog.setFatherId(channelBase.getId());
        JSONObject json = new JSONObject();
        json.put(UPDATE, decodeResult.getUpdateList().size());
        json.put(INSERT, decodeResult.getInsertList().size());
        json.put(DELETE, decodeResult.getDeleteList().size());
        channelLog.setDescr(json.toJSONString());
        return channelLog;
    }

    private static int decodeTvmaoDuration(String duration){
        int retValue = 0;
        int[] referSecond = new int[]{1,10,60,600,3600,36000};
        String strArr = duration.replaceAll(":","");
        int length = referSecond.length;
        for(int i = 0 ; i < length;i++){
            retValue +=  Integer.valueOf( strArr.charAt(length - 1 - i) - '0') * referSecond[i];
        }
        return retValue;

    }

    private static void dealResponseList(List<String> responseList) {
        Document doc = null;
        Map<String, String> params = new HashMap<String, String>();
        List<Criterion> expressionList = new ArrayList<>();
        expressionList.add(Restrictions.ne("isEnable", -1));
        expressionList.add(Restrictions.gt("qipuId", 0L));
        expressionList.add(Restrictions.eq("liveType", 1));
        List<ChannelBase> channelList = getCService().findChannelBaseList(expressionList);
        Map<String, ChannelBase> map = new HashMap<String, ChannelBase>();
        for (ChannelBase cBase : channelList) {
            map.put(cBase.getUniversalCode(), cBase);
        }
        List<ProgramData> updateList = new ArrayList<ProgramData>();
        List<ProgramData> insertList = new ArrayList<ProgramData>();
        List<ProgramData> deleteList = new ArrayList<ProgramData>();
        List<ChannelLog> channelLogList = new ArrayList<ChannelLog>();
        List<ChannelBase> updateChannelBaseList = new ArrayList<ChannelBase>();
        Map<Long, List<ProgramData>> readyToInsertMap = new HashMap<Long, List<ProgramData>>();
        for (String response : responseList) {
            if (ERROR_XML.equals(response)) {
                logger.info("response:" + response);
                continue;
            }

            doc = transToDoc(response);
            if (doc != null) {
                Element root = doc.getRootElement();
                if (root == null) {
                    logger.error("root is null; response: " + response);
                    continue;
                }
                Element channel = root.getChild(ProgramAttribute.NODE_CHANNEL);
                String uniqueCode = channel.getAttributeValue(ProgramAttribute.ATTRIBUTE_CHANNEL_CODE);
                ChannelBase channelBase = map.get(uniqueCode);
                if (channelBase == null) {
                    logger.error("channelBase is null; uniqueCode:" + uniqueCode);
                    continue;
                }
                DecodeResult decodeResult = decodeDoc(channel, channelBase);
                updateList.addAll(decodeResult.getUpdateList());
                insertList.addAll(decodeResult.getInsertList());
                deleteList.addAll(decodeResult.getDeleteList());
                channelLogList.add(createLog(channelBase, decodeResult));
                boolean needUpdate = false;
                if (decodeResult.getUpdateList().size() > 0 || decodeResult.getInsertList().size() > 0 || decodeResult.getDeleteList().size() > 0) {
                    channelBase.setIsSync(1);
                    channelBase.setEpisodeUpdateTime(new Timestamp(System.currentTimeMillis()));
                    needUpdate = true;
                }
                if (decodeResult.getInsertList().size() > 0) {
                    readyToInsertMap.put(channelBase.getId(), decodeResult.getInsertList());
                }

                if (decodeResult.isHasPrograms() && channelBase.getHasPrograms() != ChannelBase.HAS_PROGRAMS) {
                    channelBase.setHasPrograms(ChannelBase.HAS_PROGRAMS);
                    needUpdate = true;

                }
                if (needUpdate) {
                    updateChannelBaseList.add(channelBase);
                }
            }
        }
        logger.info("[dealResponseList] updateList size:" + updateList.size());
        logger.info("[dealResponseList] insertList size:" + insertList.size());
        logger.info("[dealResponseList] deleteList size:" + deleteList.size());
        logger.info("[dealResponseList]  connect db start");
        ProgramService programService = getPService();
        programService.updateFlagProgramDataList(updateList);
        programService.deleteFlagProgramDataList(deleteList);
        for (Map.Entry<Long, List<ProgramData>> entry : readyToInsertMap.entrySet()) {
            programService.saveProgramDataList(entry.getValue(), entry.getKey());
        }
        logger.info("[dealResponseList]  connect db end");

        logger.info("[save channel log ]  connect db start");
        for (ChannelLog tempLog : channelLogList) {
            getCService().saveLog(tempLog);
        }
        logger.info("[save channel log ]  connect db end");
        logger.info("[save channelbase ]  connect db start");
        for (ChannelBase channelBase : updateChannelBaseList) {
            getCService().saveBase(channelBase);
        }
        logger.info("[save channelbase ]  connect db end");

    }

    private static ProgramService getPService() {
        if (pService == null) {
            pService = (ProgramService) ApplicationContextUtil.getBean(ProgramService.class);
        }
        return pService;
    }

    private static ChannelService getCService() {
        if (cService == null) {
            cService = (ChannelService) ApplicationContextUtil.getBean(ChannelService.class);
        }
        return cService;
    }

    private static DecodeResult decodeDoc(Element channel, ChannelBase channelBase) {
        List<Element> epgChildren = (List<Element>) channel.getChildren(ProgramAttribute.NODE_EPG);
        List<Element> programChildren = null;
        ProgramData pd = null;
        List<ProgramData> formatDataList = null;
        Map<String, List<ProgramData>> map = new HashMap<String, List<ProgramData>>();
        List<String> dateList = new ArrayList<String>();
        String currentDate = DateTimeUtil.transShortFormat(System.currentTimeMillis());
        for (Element epgChild : epgChildren) {
            programChildren = (List<Element>) epgChild.getChildren(ProgramAttribute.NODE_PROGRAM);
            String date = epgChild.getAttributeValue(ProgramAttribute.ATTRIBUTE_EPG_DATE);
            if (date.compareTo(currentDate) < 0) {
                continue;
            }
            formatDataList = new ArrayList<ProgramData>();
            if (programChildren.size() > 0) {
                dateList.add(date);
                map.put(date, formatDataList);
                for (Element program : programChildren) {
                    pd = new ProgramData();
                    pd.setStartPlayTime(Timestamp.valueOf(date + " " + program.getAttributeValue(ProgramAttribute.ATTRIBUTE_PROGRAM_PLAY_TIME) + ":00"));
                    pd.setDuration( decodeTvmaoDuration(program.getAttributeValue(ProgramAttribute.ATTRIBUTE_PROGRAM_DURATION)  ) );
                    pd.setStopPlayTime(new Timestamp(pd.getStartPlayTime().getTime() +1000L*pd.getDuration() ));

                    pd.setStreamStartTime(new Timestamp(pd.getStartPlayTime().getTime()  ));
                    pd.setStreamEndTime(new Timestamp(pd.getStopPlayTime().getTime()  ));
                    pd.setPname(program.getAttributeValue(ProgramAttribute.ATTRIBUTE_PROGRAM_NAME));
                    pd.setFatherId(channelBase.getId().intValue());
                    pd.setAddTime(new Timestamp(System.currentTimeMillis()));
                    pd.setQitanId(channelBase.getQitanId());
                    pd.setMaxPlaybackPeriod( channelBase.getMaxPlaybackPeriod() );
                    pd.setSubjectUrl(channelBase.getSubjectUrl());
                    pd.setType(6);//电视台节目
                    pd.setLive(ProgramData.TYPE_LIVE);
                    pd.setMemberFree(1);
                    pd.setMemberOnly(0);
                    pd.setFreeStatus(1);
                    pd.setBossStatus(ProgramData.FREE);
                    // 继承频道 弹幕 ，评论开关
//                    pd.setAllowPopups(channelBase.getAllowPopups());
                    pd.setAllowComment(channelBase.getShouldDisplay());
                    //继承频道 弹幕 ，评论开关
                    formatDataList.add(pd);
                }
            }
            if (dateList.size() == 3) {
                break;
            }
        }

        int fatherId = channelBase.getId().intValue();
        int dateLength = dateList.size();
        List<ProgramData> toCompareList = null;
        List<ProgramData> newList = null;
        // for (int i = 0; i < 2; i++) {
        ProgramService programService = getPService();
        DecodeResult decodeResult = new DecodeResult();
        DecodeResult childResult = null;
        for (int i = 0; i < dateLength; i++) {
            toCompareList = pService.getByDay(dateList.get(i), fatherId);
            newList = map.get(dateList.get(i));
            if (i == 0 || toCompareList.size() == 0) {
                decodeResult.setHasPrograms(newList.size() > 0);
                childResult = checkDifference(toCompareList, newList);
                decodeResult.addUpdateList(childResult.getUpdateList());
                decodeResult.addInsertList(childResult.getInsertList());
                decodeResult.addDeleteList(childResult.getDeleteList());
            }
        }
        return decodeResult;
    }

    private static DecodeResult checkDifference(List<ProgramData> toCompareList, List<ProgramData> checkList) {
        int oldLength = toCompareList.size();
        int checkLength = checkList.size();
        DecodeResult decodeResult = new DecodeResult();
        ProgramData oldData = null;
        ProgramData newData = null;
        int oldIndex = 0;
        int checkIndex = 0;
        while (true) {
            if (oldIndex == oldLength || checkIndex == checkLength) {
                break;
            }
            oldData = toCompareList.get(oldIndex);
            newData = checkList.get(checkIndex);
            if (oldData.getStartPlayTime().getTime() > newData.getStartPlayTime().getTime()) {
                decodeResult.addInsert(newData);
                checkIndex++;
            } else if (oldData.getStartPlayTime().getTime() < newData.getStartPlayTime().getTime()) {
                decodeResult.addDelete(oldData);
                oldIndex++;
            } else if (!oldData.getPname().equals(newData.getPname()) || oldData.getStopPlayTime().getTime() != newData.getStopPlayTime().getTime()) {
                decodeResult.addDelete(oldData);
                decodeResult.addInsert(newData);
                oldIndex++;
                checkIndex++;
            } else {
                oldIndex++;
                checkIndex++;
            }
        }
        if (oldIndex < oldLength) {
            decodeResult.addDeleteList(toCompareList.subList(oldIndex, oldLength));

        }
        if (checkIndex < checkLength) {
            decodeResult.addInsertList(checkList.subList(checkIndex, checkLength));
        }
        return decodeResult;
    }

    public static class ProgramBasket {
        Lock lock = new ReentrantLock(); // 锁对象
        private List<String> list = null;
        private int totalLength = 0;
        private int hasCost = 0;
        private long startTime = 0;
        private long endTime = 0;
        private int responseCount = 0;
        private boolean complete = false;
        private List<String> resultList = new ArrayList<String>();

        public ProgramBasket(List<String> codeList) {
            this.list = codeList;
            totalLength = list.size();
        }

        public String pop() {
            String m = null;
            lock.lock();
            try {
                if (hasCost == 0) {
                    startTime = System.currentTimeMillis();
                }

                if (hasCost < totalLength) {
                    m = list.remove(0);
                    hasCost++;
                }
            } finally {
                lock.unlock();
            }
            return m;
        }

        public void push(String result) {
            lock.lock();
            try {
                resultList.add(result);
                responseCount++;
                if (totalLength == responseCount) {
                    if (complete) {
                        return;
                    }
                    complete = true;
                    endTime = System.currentTimeMillis();
                    logger.info("endTime - startTime :" + (endTime - startTime));
                    dealResponseList(resultList);
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public static class ProgramConsumer implements Runnable {
        ProgramBasket basket;

        public ProgramConsumer(ProgramBasket basket) {
            this.basket = basket;
        }

        public void run() {
            while (true) {
                String code = basket.pop();
                if (code == null) {
                    System.out.println("end");
                    break;
                }
                String response = getResponse(code);
                //          logger.info("code :" + code + " response :" + response);
                basket.push(response);
            }
        }
    }
}

class DecodeResult {
    private List<ProgramData> deleteList = new ArrayList<ProgramData>();
    private List<ProgramData> insertList = new ArrayList<ProgramData>();
    private List<ProgramData> updateList = new ArrayList<ProgramData>();
    private boolean hasPrograms = false;

    public DecodeResult() {
    }

    public List<ProgramData> getDeleteList() {
        return deleteList;
    }

    public List<ProgramData> getUpdateList() {
        return updateList;
    }

    public List<ProgramData> getInsertList() {
        return insertList;
    }

    public void addInsertList(List<ProgramData> list) {
        insertList.addAll(list);
    }

    public void addDeleteList(List<ProgramData> list) {
        deleteList.addAll(list);
    }

    public void addUpdateList(List<ProgramData> list) {
        updateList.addAll(list);
    }

    public void addInsert(ProgramData data) {
        insertList.add(data);
    }

    public void addDelete(ProgramData data) {
        deleteList.add(data);
    }

    public void addUpdate(ProgramData data) {
        updateList.add(data);
    }

    public boolean isHasPrograms() {
        return hasPrograms;
    }

    public void setHasPrograms(boolean hasPrograms) {
        this.hasPrograms = hasPrograms;
    }
}
